#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include <pthread.h>

#include "client_protocol.h"
#include "perror_exit.h"

w3lclient *
init_client(char *host, int port)
{
	struct sockaddr_in sock;
	struct hostent *hp;
	int sd;
	long len;
	w3lclient *w3lc;

	assert(host != NULL);
	
	memset((void *)&sock,'\0',sizeof(sock));
	sock.sin_family = AF_INET;
	sock.sin_port = htons((uint16_t)port);
	hp = gethostbyname(host);
	memcpy(hp->h_addr,&sock.sin_addr,hp->h_length);

	if((sd = socket(AF_INET,SOCK_STREAM,0))<0) {
		perror(__FILE__);
		return 0;
	}
                                                                                                                 
	if(connect(sd, (struct sockaddr *)&sock, sizeof(sock))<0) {
		perror(__FILE__);
		return 0;
	}

	if(!(w3lc = (w3lclient *)malloc(sizeof(w3lclient))))
		perror_exit("%s:%d ",__FILE__,__LINE__);
	w3lc->connected = 1;
	w3lc->socket = sd;
	
	pthread_mutex_init(&(w3lc->mutex),NULL);

	return w3lc;
}

char *
w3lRequest(w3lclient *client, char *request, ...)
{
	long len;
	char *reply;

	va_list ap;
	int d;
	char *s = NULL;
	char *msg = NULL;
	int strlenmsg = 0;
	char tmp[32];

	assert(client != NULL);
	assert(request != NULL);

	if(!(msg = (char *)malloc(sizeof(char))))
		perror_exit("%s:%d",__FILE__,__LINE__);
	msg[0]='\0';

	va_start(ap, request);
	while(*request) {
		switch(*request) {
		case '%':
			++request;
			switch(*request) {
			case 's':
				s = va_arg(ap, char *);
				break;
			case 'd':
				d = va_arg(ap, int);
				snprintf(tmp,32,"%d",d);
				s = &(tmp[0]);
				break;
			case 'c':
				d = va_arg(ap, int);
				snprintf(tmp,32,"%d",(int)d);
				s = &(tmp[0]);
			}
			break;
		default:
			snprintf(tmp,2,"%c",*request);
			s = &(tmp[0]);
		}
		if(!(msg = (char *)realloc(msg,sizeof(char)*(strlenmsg+strlen(s)+1))))
			perror_exit("%s:%d ",__FILE__,__LINE__);
		strcat(msg,s);
		strlenmsg += strlen(s);
		va_end(ap);
		++request;
	}
	
	pthread_mutex_lock(&(client->mutex));
	{
		if(request_send(client->socket,msg))
			reply = reply_read(client->socket,&len);
		else
			reply = strdup("");
	}
	pthread_mutex_unlock(&(client->mutex));

	return reply;

}

short request_send(int sd, char *data) {

	long len = 0;

	assert(sd != 0);
	assert(data != NULL);

	if(!(len = (long)strlen(data)))
		return 0;

	assert(len>0);

	if(send(sd,&len,sizeof(long),0)!=sizeof(long))
		perror_exit("%s %d",__FILE__,__LINE__);

	if(send(sd,data,len,0)!=len)
		perror_exit("%s %d",__FILE__,__LINE__);

	return 1;
}

/*!
 * \brief Read a reply from the w3l daemon
 * @param sd a live socket descriptor
 * @return a printable string representing the returned data
 */
char *reply_read(int sd,long *len) {
	
	char *reply = NULL;
	long rec,tmp;
	char dtype;

	assert(sd!=0);

	if(recv(sd,len,sizeof(long),0)!=sizeof(long))
		perror_exit("%s %d",__FILE__,__LINE__);
	assert(*len>=0);

	if(recv(sd,&dtype,sizeof(char),0)!=sizeof(char))
		perror_exit("%s %d - %s",__FILE__,__LINE__,"Reading data type");
	assert(dtype>=0);

	switch(dtype) {
	case DATA_TYPE_ERROR:
	case DATA_TYPE_GRAPH:
	case DATA_TYPE_STRING:
	case DATA_TYPE_INT:
		MALLOCATE(reply,char *,sizeof(char)*(*len+1))
		rec = 0;
		while(rec < *len) {
			if((tmp=recv(sd,&(reply[rec]),(*len-rec),0))<0)
				perror_exit("%s %d - %s",__FILE__,__LINE__,"Reading Reply");
			rec+=tmp;
		}
		assert(rec == *len);
		reply[rec]='\0';
		break;
	case DATA_TYPE_LIST:
		rec = 0;
		while(rec < *len) {
			reply = concatenate(reply, reply_read(sd,&tmp));
			reply = concatenate(reply,"\n");
			rec += tmp + sizeof(long) + sizeof(char);
		}
		assert(rec == *len);
		break;
	}

	if(reply == NULL)
		reply = strdup("");

	return reply;	
}
